#include "ET++.ph"
#include "Converter.h"

#include "Class.h"
#include "VObjectText.h"
#include "VObjectTView.h"
#include "TextReader.h"
#include "TextMarks.h"
#include "BorderItems.h"
#include "String.h"
#include "Filler.h"
#include "Picture.h"
#include "PictureItem.h"
#include "Data.h"
#include "Stream.h"
#include "Alert_e.h"
#include "Env.h"
#include "HexFilter.h"
#include "MemBuf.h"
#include "TypeMatcher.h"
#include "RunArray.h"
#include "TextStyles.h"
#include "CType.h"

static const Symbol
	cDocTypeMacPict("MACPICT"),
	cDocTypeRtf("RTF");

static TypeMatcher matcher(cDocTypeRtf, "rtf", "{\\rtf", TRUE);

byte ansimap[]= {
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
	0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
	0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
	0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
	0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
	0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
	0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
	0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
	0xa8, 0xa9, 0xd2, 0xab, 0xac, 0xad, 0xae, 0xaf,
	0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xa5,
	0xb8, 0xb9, 0xd3, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
	0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
	0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
	0xd1, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
	0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
	0xe0, 0xe1, 0xe2, 0xa9, 0xe4, 0xe5, 0xe6, 0xe7,
	0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
	0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};

byte macmap[]= {
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
	0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
	0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
	0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
	0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
	0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
	0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
	0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
	0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
	0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
	0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
	0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
	0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
	0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
	0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
	0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
	0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
	0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};

//---- RTF control Words -------------------------------------------------------

const int
		cPar    =   1,
		cLine   =   2,
		cTTab   =   3,
		cFont   =   4,
		cColor  =   5,
		cSize   =   6,
		cFace   =   7,
		cCPlain =   8,
		cMacMap =   9,
		cNoUnder=   10,
		cFontTbl=   12,
		cColorTbl=  13,
		cPict   =   14,
		cRTF    =   15,
		cIgnore =   16,
		cRGB    =   17,
		cPicSize=   18,
		cMacPict=   19,
		cFontStyle= 20,
		cNextPict=  21,
		cHeader =   22,
		cFooter =   23,
		cFootNote=  24,
		cPard   =   25,
		cPCenter=   26,
		cPJust  =   27,
		cPLeft  =   28,
		cPRight =   29,
		cPFInd  =   30,
		cPLInd  =   31,
		cPRInd  =   32,
		cPSpAfter=  33,
		cPSpBefore= 34,
		cPSpLine =  35,
		cPage   =   36,
		cTabPosL=   37,
		cTabPosR=   38,
		cTabPosC=   39,
		cTabPosD=   40,
		cTabDef =   41,
		cHidden =   42,
		cNonBrSpace=43,
		cNonBrHyphen=44,
		cOptHyphen= 45,
		cETVobject= 46,
		cETVobjComment= 47,
		cVobjStart= 48,
		cPicGoal =  49,
		cPKeep   =  50,
		cPKeepN  =  51;


//---- font table --------------------------------------------------------------

struct FTab {
	int id;
	GrFont eid;
	char *name;
};

//---- control Word action table -----------------------------------------------

static struct stab {
	char *name;
	int id, attr;
} tab[] = {     // keep sorted
	{ "NeXTGraphic",  cNextPict,   0 },
	{ "b",            cFace,       (int) eFaceBold },
	{ "blue",         cRGB,        2 },
	{ "cell",         cTTab,       0 },
	{ "cf",           cColor,      0 },
	{ "colortbl",     cColorTbl,   0 },
	{ "deftab",       cTabDef,     0 },
	{ "etvobject",    cETVobject,  0 },
	{ "f",            cFont,       0 },
	{ "fi",           cPFInd,      0 },
	{ "fmodern",      cFontStyle,  2 },
	{ "fnil",         cFontStyle,  0 },
	{ "fonttbl",      cFontTbl,    0 },
	{ "footer",       cFooter,     0 },
	{ "footerl",      cFooter,     1 },
	{ "footerr",      cFooter,     2 },
	{ "footnote",     cFootNote,   0 },
	{ "froman",       cFontStyle,  0 },
	{ "fs",           cSize,       0 },
	{ "fswiss",       cFontStyle,  1 },
	{ "ftech",        cFontStyle,  3 },
	{ "green",        cRGB,        1 },
	{ "header",       cHeader,     0 },
	{ "headerl",      cHeader,     1 },
	{ "headerr",      cHeader,     2 },
	{ "height",       cPicSize,    1 },
	{ "i",            cFace,       (int) eFaceItalic },
	{ "info",         cIgnore,     0 },
	{ "keep",         cPKeep,      0 },
	{ "keepn",        cPKeepN,     0 },
	{ "li",           cPLInd,      0 },
	{ "line",         cLine,       0 },
	{ "mac",          cMacMap,     0 },
	{ "macpict",      cMacPict,    0 },
	{ "outl",         cFace,       (int) eFaceOutline },
	{ "page",         cPage,       0 },
	{ "par",          cPar,        0 },
	{ "pard",         cPard,       0 },
	{ "pich",         cPicSize,    1 },
	{ "pichGoal",     cPicGoal,    1 },
	{ "pict",         cPict,       0 },
	{ "picw",         cPicSize,    0 },
	{ "picwGoal",     cPicGoal,    0 },
	{ "plain",        cCPlain,     0 },
	{ "qc",           cPCenter,    0 },
	{ "qj",           cPJust,      0 },
	{ "ql",           cPLeft,      0 },
	{ "qr",           cPRight,     0 },
	{ "red",          cRGB,        0 },
	{ "ri",           cPRInd,      0 },
	{ "row",          cPar,        0 },
	{ "rtf",          cRTF,        0 },
	{ "sa",           cPSpAfter,   0 },
	{ "sb",           cPSpBefore,  0 },
	{ "sect",         cPar,        0 },
	{ "shad",         cFace,       (int) eFaceShadow },
	{ "sl",           cPSpLine,    0 },
	{ "stylesheet",   cIgnore,     0 },
	{ "tab",          cTTab,       0 },
	{ "tqc",          cTabPosC,    0 },
	{ "tqdec",        cTabPosD,    0 },
	{ "tqr",          cTabPosR,    0 },
	{ "tx",           cTabPosL,    0 },
	{ "ul",           cFace,       (int) eFaceUnderline },
	{ "uld",          cFace,       (int) eFaceUnderline },
	{ "uldb",         cFace,       (int) eFaceUnderline },
	{ "ulnone",       cNoUnder,    0 },
	{ "ulw",          cFace,       (int) eFaceUnderline },
	{ "v",            cIgnore,     0 },
	{ "vobjfollows",  cETVobjComment, 0 },
	{ "vobjstart",    cVobjStart,  0 },
	{ "width",        cPicSize,    0 }
};

static stab *find(const char *name)
{
	register int position;
	int last= sizeof(tab)/sizeof(stab)-1, base= 0, result;
	if (tab && name) {
		while (last >= base) {
			position= (base+last) >> 1;
			if ((result= strcmp(tab[position].name, name)) == 0)
				return &tab[position];
			if (result > 0)
				last= position-1;
			else
				base= position+1;
		}
	}
	return 0;
}

inline int TwipsToPix(int t)
	{ return t/20; }

//---- RTF_Dest ----------------------------------------------------------------

class RTF_Global;
class RTF_State;

class RTF_Dest {
public:
	RTF_Dest(RTF_Dest *b);
	virtual ~RTF_Dest()
		{ }
	virtual void Word(RTF_State*, int, int, int)
		{ }
	virtual RTF_Dest *MakeDestination(RTF_State*, int);

	virtual void Out(RTF_State*, byte)
		{ }
	virtual void InsertPicture(Picture*, Point)
		{ }
	virtual void InsertText(Text*)
		{ }
	virtual void InsertVObject(VObject*)
		{ }
	virtual void InsertVisualMark(VisualMark*)
		{ }

	RTF_Global *GetGlobal()
		{ return global; }
protected:
	RTF_Dest *base;
	RTF_Global *global;
};

//---- RTF_Global --------------------------------------------------------------

class RTF_Global : public RTF_Dest {
public:
	Class *textClass;
	Data *src;
	Text *body;
	byte *charmap;
	int cx;
	Ink *ctab[256];
	struct FTab ftab[50];
	bool ignoreVObjects;
	bool askForVObjects;

public:
	RTF_Global(Class *want, Data *d);
	~RTF_Global()
		{ }
	void Word(RTF_State *d, int code, int num, int attr);
	void Out(RTF_State*, byte)
		{ }
	void InsertText(Text *t)
		{ body= t; }
	bool IgnoreVObjects();
	Class *TextClass()
		{ return textClass; }
	Data *SourceData()
		{ return src; }
	GrFont FindFont(int id);
	Ink *FindInk(int colorid);
};

RTF_Global::RTF_Global(Class *want, Data *dp) : RTF_Dest(0)
{
	global= this;
	charmap= ansimap;
	cx= 0;
	askForVObjects= TRUE;
	textClass= want;
	src= dp;
	ctab[cx++]= gInkBlack;
}

void RTF_Global::Word(RTF_State*, int, int, int)
{
}

GrFont RTF_Global::FindFont(int id)
{
	for (int i= 0; ftab[i].name; i++)
		if (ftab[i].id == id)
			return ftab[i].eid;
	return eFontTimes;
}

Ink *RTF_Global::FindInk(int colorid)
{
	return ctab[colorid];
}

bool RTF_Global::IgnoreVObjects()
{
	if (askForVObjects) {
			switch (ShowAlert(eAlertCaution, "Read VObjects in RTF ?")) {
			case cIdYes:
			case cIdCancel:
				ignoreVObjects= FALSE;
				break;
			case cIdNo:
				ignoreVObjects= TRUE;
				break;
			}
		}
	askForVObjects= FALSE;
	return ignoreVObjects;
}

//---- RTF_Global --------------------------------------------------------------

class RTF_GlobalPureText : public RTF_Global {
public:
	RTF_GlobalPureText(Class *want, Data *dp);
	~RTF_GlobalPureText()
		{ }
	RTF_Dest *MakeDestination(RTF_State *d, int code);
};

//---- RTF_DocDest -------------------------------------------------------------

class RTF_DocDest : public RTF_Dest {
public:
	RTF_DocDest(RTF_Dest *b);
	~RTF_DocDest();
	void Word(RTF_State *d, int code, int num, int attr);
	void Out(RTF_State*, byte c);
	void InsertPicture(Picture*, Point e);
	void InsertText(Text *op);
	void InsertVObject(VObject *vop);
	void InsertVisualMark(VisualMark *gip);

private:
	StyledText *vt;
	TextReader *reader;
	GrFont font;
	Ink *ink;

	void FlushParagraph(RTF_State *d);
	void OutVObjectPlaceHolder(RTF_State *d);
};

//---- RTF_AsciiDest -----------------------------------------------------------

class RTF_AsciiDest : public RTF_Dest {
public:
	RTF_AsciiDest(RTF_Dest *b);
	~RTF_AsciiDest();
	RTF_Dest *MakeDestination(RTF_State *d, int code);
	void Word(RTF_State *d, int code, int num, int attr);
	void Out(RTF_State*, byte c);
private:
	Text *gt;
};

//---- RTF_FontTable -----------------------------------------------------------

class RTF_FontTable : public RTF_Dest {
public:
	RTF_FontTable(RTF_Dest *b) : RTF_Dest(b)
		{ ix= 0; fx= 0; }
	~RTF_FontTable();
	void Word(RTF_State *d, int code, int num, int attr);
	void Out(RTF_State*, byte c);

private:
	int fx, ix;
	char name[100];
};
		
//---- RTF_ColorTable ----------------------------------------------------------

class RTF_ColorTable : public RTF_Dest {
public:
	RTF_ColorTable(RTF_Dest *b);
	void Word(RTF_State *d, int code, int num, int attr);
	void Out(RTF_State*, byte c);
private:
	int rgb[3];
};
		
//---- RTF_MacPict -------------------------------------------------------------

class RTF_MacPict : public RTF_Dest {
public:
	RTF_MacPict(RTF_Dest *b);
	void Word(RTF_State *d, int code, int num, int attr);
	void Out(RTF_State*, byte c);
protected:
	Point e;
	bool inTwips, readpict;
};
		
//---- RTF_ETVObject -----------------------------------------------------------

class RTF_ETVObject : public RTF_Dest {
public:
	RTF_ETVObject(RTF_Dest *b);
	void Word(RTF_State *d, int code, int num, int attr);
};
		
//---- RTF_IgnoreETVObject -----------------------------------------------------

class RTF_IgnoreETVObject : public RTF_Dest {
public:
	RTF_IgnoreETVObject(RTF_Dest *b);
	void Word(RTF_State *d, int code, int num, int attr);
};
		
//---- RTF_NeXTPict ------------------------------------------------------------

class RTF_NeXTPict : public RTF_Dest {
public:
	RTF_NeXTPict(RTF_Dest *b);
	~RTF_NeXTPict();
	void Word(RTF_State *d, int code, int num, int attr);
	void Out(RTF_State *s, byte b);
protected:
	Point e;
	char *fileName;
	int fi;
};

//---- RTF_State ---------------------------------------------------------------

class RTF_State {
public:
	GrFace _face;
	GrFont _font;
	Ink *_color;
	int _size;
	TxtParaProp tabmode;
	ParaDesc parProp;
	IStream *readfrom;
public:
	RTF_State(RTF_Dest*);
	RTF_State(RTF_State *super);
	~RTF_State();
	void PlainChar();
	void SetFont(int f);
	void SetColor(int c);
	void SetSize(int s)
		{ _size= s; }
	void SetTabMode(TxtParaProp tm)
		{ tabmode= tm; }
	void SetFace(GrFace fc, int b)
		{ _face= (GrFace) (b ? _face | fc : _face & ~fc); }
	void SetFace(GrFace fc)
		{ _face= fc; }
		
	void SetParProp(TxtParaProp what, int value);
	void PlainPar();
	void CopyParProp(ParaDesc &p);
	void CopyCharProp(GrFont &f, int &s, GrFace &fc, Ink *&i);
	void Out(byte c)
		{ dest->Out(this, c); }
	void Word(int code, int num, int attr);
	void scan(IStream &s);
	IStream *GetStream();

private:
	RTF_State *super;
	RTF_Dest *dest;
};

//---- RTF_Dest ----------------------------------------------------------------

RTF_Dest::RTF_Dest(RTF_Dest *b)
{
	base= b;
	if (base)
		global= base->global;
}

RTF_Dest *RTF_Dest::MakeDestination(RTF_State *d, int code)
{
	RTF_Dest *dest;

	switch (code) {
	case cIgnore:
		dest= new RTF_Dest(this);  // skip
		break;
	case cFontTbl:
		dest= new RTF_FontTable(this);
		break;
	case cColorTbl:
		dest= new RTF_ColorTable(this);
		break;
	case cRTF:
		dest= new RTF_DocDest(this);
		break;
	case cPict:
		dest= new RTF_MacPict(this);
		break;
	case cETVobject:
		if (!GetGlobal()->IgnoreVObjects())
			dest= new RTF_ETVObject(this);
		else
			dest= new RTF_IgnoreETVObject(this);
		break;
	case cETVobjComment:
		if (GetGlobal()->IgnoreVObjects())
			Word(d, cETVobject, 0, 0);
		dest= new RTF_Dest(this);  // skip
		break;
	case cNextPict:
		dest= new RTF_NeXTPict(this);
		break;
	case cHeader:
	case cFooter:
	case cFootNote:
		dest= new RTF_Dest(this);  // skip
//        dest= new RTF_DocDest(this);
		break;
	default:
		dest= new RTF_Dest(this);  // skip
		break;
	}
	return dest;
}
		
void IgnoreVObjects()
{
}

//---- RTF_State ---------------------------------------------------------------

RTF_State::RTF_State(RTF_Dest *d)
{
	super= 0;
	dest= d;
	PlainChar();
	PlainPar();
}

RTF_State::RTF_State(RTF_State *sup)
{
	super= sup;
	dest= super->dest;
	super->CopyCharProp(_font, _size, _face, _color);
	super->CopyParProp(parProp);
}

RTF_State::~RTF_State()
{
	if (super && dest != super->dest)
		SafeDelete(dest);
}

void RTF_State::SetFont(int f)
{
	_font= dest->GetGlobal()->FindFont(f);
}

void RTF_State::SetColor(int c)
{
	_color= dest->GetGlobal()->FindInk(c);
}

void RTF_State::CopyParProp(ParaDesc &p)
{
	p= parProp;
}

void RTF_State::CopyCharProp(GrFont &f, int &s, GrFace &fc, Ink *&i)
{
	f= _font;
	s= _size;
	fc= _face;
	i= _color;
}

void RTF_State::PlainChar()
{
	SetFont(0);
	SetSize(12);
	SetColor(0);
	SetFace(eFacePlain);
}

void RTF_State::PlainPar()
{
	SetParProp(eTxtPAlign, (int)eTxtParaLeft);
	SetParProp(eTxtPLeft,  0);
	SetParProp(eTxtPRight, 0);
	SetParProp(eTxtPFirstIndent, 0);
	SetParProp(eTxtPSpacing, 12);
	SetParProp(eTxtPSpaceBefore, 0);
	SetParProp(eTxtPSpaceAfter, 0);
	SetParProp(eTxtPEmptyTabs, 0);
	SetParProp(eTxtPNoBreak, 0);
	SetParProp(eTxtPKeepNext, 0);
	SetTabMode(eTxtPAddTab);
}

void RTF_State::SetParProp(TxtParaProp what, int value)
{
	parProp.SetProperty(what, value);
}

void RTF_State::Word(int code, int num, int attr)
{
	switch (code) {
	case cIgnore:
	case cFontTbl:
	case cColorTbl:
	case cRTF:
	case cPict:
	case cETVobject:
	case cETVobjComment:
	case cNextPict:
	case cHeader:
	case cFooter:
	case cFootNote:
		PlainChar();
		dest= dest->MakeDestination(this, code);
		break;
	default:
		if (dest)
			dest->Word(this, code, num, attr);
		break;
	}
}

void RTF_State::scan(IStream &is)
{
	byte c, buf[100];
	bool ignoreDest= FALSE;
	int i, minus, num;

	readfrom= &is;
	while (is.get(c)) {
		switch (c) {
		case '\011':    // tab
			Word(cTTab, 0, 0);
			continue;
		case '\012':
		case '\015':
			continue;
		case '{':
			{
				RTF_State(this).scan(is);
			}
			break;
		case '}':
			return;
		case '\\':
			is.get(c);
			switch (c) {
			case '*':
				ignoreDest= TRUE;
				continue;
			case '\n':
				Word(cPar, 0, 0);
				continue;
			case '~':
				Word(cNonBrSpace, 0, 0);
				continue;
			case '_':
				Word(cNonBrHyphen, 0, 0);
				continue;
			case '-':
				Word(cOptHyphen, 0, 0);
				continue;
			case ':':
			case '{':
			case '}':
			case '\\':
				Out(c);
				continue;
			case '\'':
				Out(is.GetHex());
				continue;
			default:
				if (Isalpha(c)) {
					i= num= minus= 0;
					do {
						buf[i++]= c;
						is.get(c);
					} while (Isalpha(c));

					switch (c) {
					case ' ':
						num= 1;
						break;
					case '-':
						is.get(c);
						minus= 1;
					case '0': case '1': case '2': case '3': case '4':
					case '5': case '6': case '7': case '8': case '9':
						do {
							num= num*10+c-'0';
							is.get(c);
						} while (Isdigit(c));
						if (c != ' ')
							is.putback(c);
						break;
					default:
						num= 1;
						is.putback(c);
					}

					buf[i]= '\0';
					if (minus)
						num= -num;
					stab *st= find((char*)buf);
					if (st)
						Word(st->id, num, st->attr);
					else if (ignoreDest)
						Word(cIgnore, 0, 0);
					ignoreDest= FALSE;
				} else
					Out(c);
				break;
			}
			break;
		default:
			Out(c);
			break;
		}
	}
}

IStream *RTF_State::GetStream()
{
	return readfrom;
}

//---- RTF_GlobalPureText ------------------------------------------------------

RTF_GlobalPureText::RTF_GlobalPureText(Class *want, Data *dp) : RTF_Global(want, dp)
{
}

RTF_Dest *RTF_GlobalPureText::MakeDestination(RTF_State *d, int code)
{
	switch (code) {
	case cRTF:
		return new RTF_AsciiDest(this);
	default:
		return RTF_Global::MakeDestination(d, code);
	}
}

//---- RTF_DocDest -------------------------------------------------------------

RTF_DocDest::RTF_DocDest(RTF_Dest *b) : RTF_Dest(b)
{
	ink= ePatBlack;
	font= global->FindFont(0);
	vt= (StyledText*)global->TextClass()->Proto()->New();
	reader= new TextReader(
					vt,
					new_CharStyle(font, eFacePlain, 12, ink),
					new_ParaStyle()
				);
}

RTF_DocDest::~RTF_DocDest()
{
	reader->Flush();
	SafeDelete(reader);

	if (base)
		base->InsertText(vt);
}

void RTF_DocDest::Word(RTF_State *d, int code, int num, int attr)
{
	switch (code) {
	case cETVobject:
		OutVObjectPlaceHolder(d);
		break;
	case cPCenter:
		d->SetParProp(eTxtPAlign, eTxtParaCenter);
		break;
	case cPJust:
		d->SetParProp(eTxtPAlign, eTxtParaJustified);
		break;
	case cPLeft:
		d->SetParProp(eTxtPAlign, eTxtParaLeft);
		break;
	case cPRight:
		d->SetParProp(eTxtPAlign, eTxtParaRight);
		break;
	case cPage:
		Out(d, '\f');
		break;
	case cPard:
		d->PlainPar();
		break;
	case cPar:
		FlushParagraph(d);
		Out(d, '\n');
		break;
	case cNonBrSpace:
		Out(d, ' ');
		break;
	case cNonBrHyphen:
		Out(d, '-');
		break;
	case cOptHyphen:
		break;
	case cLine:
		Out(d, '\r');
		break;
	case cTTab:
		Out(d, '\t');
		break;
	case cPFInd:
		d->SetParProp(eTxtPFirstIndent, TwipsToPix(num));
		break;
	case cPLInd:
		d->SetParProp(eTxtPLeft, TwipsToPix(num));
		break;
	case cPRInd:
		d->SetParProp(eTxtPRight, TwipsToPix(num));
		break;
	case cPSpAfter:
		d->SetParProp(eTxtPSpaceAfter, TwipsToPix(num));
		break;
	case cPKeep:
		d->SetParProp(eTxtPNoBreak, 1);
		break;
	case cPKeepN:
		d->SetParProp(eTxtPKeepNext, 1);
		break;
	case cTabPosL:
		d->SetParProp(d->tabmode, TwipsToPix(num));
		d->SetTabMode(eTxtPAddTab);
		break;
	case cTabPosC:
		d->SetTabMode(eTxtPAddTabC);
		break;
	case cTabPosD:
		d->SetTabMode(eTxtPAddTabD);
		break;
	case cTabPosR:
		d->SetTabMode(eTxtPAddTabR);
		break;
	case cTabDef:
		vt->SetDefTab(TwipsToPix(num));
		break;
	case cPSpBefore:
		d->SetParProp(eTxtPSpaceBefore, TwipsToPix(num));
		break;
	case cPSpLine:
		d->SetParProp(eTxtPSpacing, TwipsToPix(num));
		break;
	case cFont:
		d->SetFont(num);
		break;
	case cColor:
		d->SetColor(num);
		break;
	case cSize:
		d->SetSize(num/2);
		break;
	case cCPlain:
		d->PlainChar();
		break;
	case cFace:
		d->SetFace((GrFace)attr, num);
		break;
	case cNoUnder:
		d->SetFace(eFaceUnderline, 0);
		break;
	case cMacMap:
		global->charmap= macmap;
		break;
	}
}

void RTF_DocDest::Out(RTF_State *d, byte c)
{
	if (d->_font != reader->GetFont())
		reader->SetFont(d->_font);
	if (d->_size != reader->GetSize())
		reader->SetSize(d->_size);
	if (d->_face != reader->GetFace())
		reader->SetFace(d->_face);
	if (d->_color != reader->GetInk())
		reader->SetInk(d->_color);
	reader->Append(global->charmap[c]);
}

void RTF_DocDest::OutVObjectPlaceHolder(RTF_State *d)
{
	byte *msg= (byte*)"[***ET++ Image***]";
	for(byte *p= msg; *p != '\0'; p++)
		Out(d, *p);
}

void RTF_DocDest::FlushParagraph(RTF_State *d)
{
	reader->SetParaProp(d->parProp);
}

void RTF_DocDest::InsertPicture(Picture *pic, Point e)
{
	VObject *vop;
	if (pic)
		vop= new PictureItem(pic, e);
	else
		vop= new Filler(e, ePatGrey50);
	InsertVObject(vop);
}

void RTF_DocDest::InsertVObject(VObject *vop)
{
	reader->InsertVObject(vop);
}

void RTF_DocDest::InsertVisualMark(VisualMark *gip)
{
	reader->InsertVisualMark(gip);
}

void RTF_DocDest::InsertText(Text *t)
{
	if (vt->IsKindOf(VObjectText)) {
		VObject *vop= new BorderItem(new VObjectTextView(0, Point(cFit,cFit),
								(VObjectText*)t));
		reader->InsertVObject(vop);
	}
}

//---- RTF_AsciiDest -----------------------------------------------------------

RTF_AsciiDest::RTF_AsciiDest(RTF_Dest *b) : RTF_Dest(b)
{
	gt= (Text*)global->TextClass()->Proto()->New();
}

RTF_AsciiDest::~RTF_AsciiDest()
{
	if (base)
		base->InsertText(gt);
}

RTF_Dest *RTF_AsciiDest::MakeDestination(RTF_State *d, int code)
{
	switch (code) {
	case cHeader:
	case cFooter:
	case cFootNote:
		return new RTF_Dest(this);
	default:
		return RTF_Dest::MakeDestination(d, code);
	}
}

void RTF_AsciiDest::Word(RTF_State *d, int code, int, int)
{
	switch (code) {
	case cPage:
		Out(d, '\f');
		break;
	case cPar:
		Out(d, '\n');
		Out(d, '\n');
		break;
	case cNonBrSpace:
		Out(d, ' ');
		break;
	case cNonBrHyphen:
		Out(d, '-');
		break;
	case cOptHyphen:
		break;
	case cLine:
		Out(d, '\n');
		break;
	case cTTab:
		Out(d, '\t');
		break;
	case cMacMap:
		global->charmap= macmap;
		break;
	}
}

void RTF_AsciiDest::Out(RTF_State*, byte c)
{
	if (gt->IsKindOf(GapText)) {   // hack
		GapText *t= (GapText*)gt;
		t->AddChar(gt->End(), global->charmap[c]);
	} else
		gt->Append(global->charmap[c]);
}

//---- RTF_FontTable -----------------------------------------------------------

static struct fontnames {
	char *name;
	GrFont font;
} FontNames[] = {
	{ "Times",            eFontTimes },
	{ "Helvetica",        eFontHelvetica },
	{ "Courier",          eFontCourier },
	{ "Symbol",           eFontSymbol },
	{ "Chicago",          eFontChicago },
	{ "Avantgarde",       eFontAvantgarde },
	{ "Bookman",          eFontBookman },
	{ "Schoolbook",       eFontSchoolbook },
	{ "NHelvetica",       eFontNHelvetica },
	{ "Palatino",         eFontPalatino },
	{ "Chancery",         eFontChancery },
	{ "Dingbats",         eFontDingbats },
	{ 0,                  eFontDingbats }
};

RTF_FontTable::~RTF_FontTable()
{
	for (int i= 0; i < fx; i++) {
		for (int j= 0; FontNames[j].name; j++) {
			if(strcmp(FontNames[j].name, global->ftab[i].name) == 0) {
				global->ftab[i].eid= FontNames[j].font;
				break;
			}
		}
	}
}

void RTF_FontTable::Out(RTF_State*, byte c)
{
	if (c == ';') {
		name[ix]= 0;
		ix= 0;
		global->ftab[fx++].name= strsave(name);
	} else
		name[ix++]= c;
}

static GrFont ft[] = {
	eFontTimes,
	eFontHelvetica,
	eFontCourier,
	eFontSymbol
};

void RTF_FontTable::Word(RTF_State*, int code, int num, int attr)
{
	switch (code) {
	case cFont:
		global->ftab[fx].id= num;
		break;
	case cFontStyle:
		global->ftab[fx].eid= ft[attr];
		break;
	}
}

//---- RTF_ColorTable ----------------------------------------------------------

RTF_ColorTable::RTF_ColorTable(RTF_Dest *b) : RTF_Dest(b)
{
	rgb[0]= rgb[1]= rgb[2]= global->cx= 0;
}

void RTF_ColorTable::Out(RTF_State*, byte c)
{
	if (c == ';') {
		if (rgb[0] == 0 && rgb[1] == 0 && rgb[2] == 0)
			global->ctab[global->cx]= gInkBlack;
		else if (rgb[0] == 0 && rgb[1] == 0 && rgb[2] == 0)
			global->ctab[global->cx]= gInkWhite;
		else
			global->ctab[global->cx]= new_RGBColor(rgb[0], rgb[1], rgb[2]);
		global->cx++;
		rgb[0]= rgb[1]= rgb[2]= 0;
	}
}

void RTF_ColorTable::Word(RTF_State*, int code, int num, int attr)
{
	if (code == cRGB)
		rgb[attr]= num;
}

//---- RTF_MacPict -------------------------------------------------------------

RTF_MacPict::RTF_MacPict(RTF_Dest *b) : RTF_Dest(b)
{
	readpict= FALSE;
	inTwips= TRUE;
}

void RTF_MacPict::Word(RTF_State*, int code, int num, int attr)
{
	switch (code) {
	case cMacPict:
		readpict= TRUE;
		inTwips= FALSE;
		break;
		
	case cPicSize:
	case cPicGoal:
		if (inTwips)
			num= TwipsToPix(num);
		if (attr == 0)
			e.x= num;
		else
			e.y= num;
		// e[attr]= inTwips ? TwipsToPix(num) : num;
	}
}

void RTF_MacPict::Out(RTF_State *s, byte b)
{
	if (readpict) {
		MemBuf mb(BUFSIZE*4, new char[BUFSIZE*4]);
		OStream os(&mb);
		IStream *isp= s->GetStream();

		isp->putback(b);
		isp->Push(new HexDecoder);
		os << (*isp);
		isp->Pop();

		base->InsertVisualMark(new ReferenceMark(
			new MemoryData((byte*)mb.Base(), (int)mb.tell(), cDocTypeMacPict, FALSE, TRUE), e));
	}
}

//---- RTF_ETVObject -----------------------------------------------------------

RTF_ETVObject::RTF_ETVObject(RTF_Dest *b) : RTF_Dest(b)
{
}

void RTF_ETVObject::Word(RTF_State *s, int code, int, int)
{
	if (code == cVobjStart) {
		IStream oios(s->GetStream()->getsbuf(), 0, FALSE);
		VisualMark *vmp= 0;
		oios >> vmp;
		if (vmp)
			base->InsertVisualMark(vmp);
	}
}

//---- RTF_NeXTPict ------------------------------------------------------------

RTF_NeXTPict::RTF_NeXTPict(RTF_Dest *b) : RTF_Dest(b)
{
	fileName= 0;
}

RTF_NeXTPict::~RTF_NeXTPict()
{
	if (fileName) {
		fileName[fi-1]= '\0'; // strip trailing blank
		static char buf[1000];
		strcpy(buf, GetGlobal()->SourceData()->FullName());
		char *dirName= strrchr(buf, '/');
		if (dirName)
			*dirName= '\0';
		if (strcmp(&fileName[strlen(fileName)-4], ".eps") == 0)
			base->InsertVObject(new Filler(e, ePatGrey50));
		else
			base->InsertVisualMark(new ReferenceMark(form("%s/%s", buf, fileName), e));
		SafeDelete(fileName);
	}
}

void RTF_NeXTPict::Word(RTF_State*, int code, int num, int attr)
{
	if (code == cPicSize) {
		num= TwipsToPix(num);
		if (attr == 0)
			e.x= num;
		else
			e.y= num;
	}
}

void RTF_NeXTPict::Out(RTF_State*, byte b)
{
	if (fileName == 0) {
		fileName= new char[1000];
		fi= 0;
	}
	fileName[fi++]= b;
}

//---- RTF_IgnoreETVObject -----------------------------------------------------

RTF_IgnoreETVObject::RTF_IgnoreETVObject(RTF_Dest *b) : RTF_Dest(b)
{
}

void RTF_IgnoreETVObject::Word(RTF_State *s, int code, int, int)
{
	if (code == cVobjStart) {
		int br= 0;
		char c;
		IStream *is= s->GetStream();
		while ((*is).get(c)) {
			if (c == '{')
				br++;
			else if (c == '}') {
				br--;
				if(br == 0)
					break;
			}
		}
	}
}

//---- RTF_FontMap -------------------------------------------------------------

const int cMaxFonts= 100;

static struct familytab {
	GrFont font;
	char *family;
} famtab[] = {
	{ eFontTimes,         "froman" },
	{ eFontHelvetica,     "fswiss" },
	{ eFontCourier,       "fmodern" },
	{ eFontSymbol,        "ftech" },
	{ eFontChicago,       "fswiss" },
	{ eFontAvantgarde,    "fswiss" },
	{ eFontBookman,       "froman" },
	{ eFontSchoolbook,    "fnil" },
	{ eFontNHelvetica,    "fswiss" },
	{ eFontPalatino,      "froman" },
	{ eFontChancery,      "fdecor" },
	{ eFontDingbats,      "fdecor" },
	{ eFontDingbats,      0 }
};

class RTF_FontMap {
	int nfonts;
	GrFont font[cMaxFonts];
public:
	RTF_FontMap(GrFont dflt= eFontDefault);
	int Add(GrFont fnt);
	int Map(GrFont fnt);
	char *Family(GrFont fnt);
};

RTF_FontMap::RTF_FontMap(GrFont)
{
	nfonts= 0;
}

int RTF_FontMap::Add(GrFont fnt)
{
	for (int i= 0; i < nfonts; i++)
		if (font[i] == fnt)
			return -1;
	if (i < cMaxFonts) {
		font[nfonts++]= fnt;
		return i;
	}
	return -1;
}

int RTF_FontMap::Map(GrFont fnt)
{
	for (int i= 0; i < nfonts; i++)
		if (font[i] == fnt)
			return i;
	return 0;
}

char *RTF_FontMap::Family(GrFont fnt)
{
	for (int i= 0; famtab[i].family != 0; i++)
		if (famtab[i].font == fnt)
			return famtab[i].family;
	return "fnil";
}

//---- RTF_InkMap --------------------------------------------------------------

const int cMaxInks= 100;

class RTF_InkMap {
	int nInks;
	Ink *inks[cMaxInks];
public:
	RTF_InkMap();
	int Add(Ink *);
	int Map(Ink *);
};

RTF_InkMap::RTF_InkMap()
{
	nInks= 0;
}

int RTF_InkMap::Add(Ink *ink)
{
	for (int i= 0; i < nInks; i++)
		if (ink->IsEqual(inks[i]))
			return -1;
	if (i < cMaxInks) {
		inks[nInks++]= ink;
		return i;
	}
	return -1;
}

int RTF_InkMap::Map(Ink *ink)
{
	for (int i= 0; i < nInks; i++)
		if (ink->IsEqual(inks[i]))
			return i;
	return 0;
}

//---- RTF_Writer --------------------------------------------------------------

class RTF_Writer {
public:
	RTF_Writer(OStream &os);
	~RTF_Writer();
	void Doit(StyledText *st);
private:
	OStream &os;
	StyledText *st;
	int charpos;     // approximate character position on a line
	RTF_FontMap *fm;
	RTF_InkMap *im;

	GrFace face;
	GrFont font;
	int size;
	Ink *color;

	byte vobjectEscape;
	bool ignoreVObjects;

	void Begin();
	void End();
	void FontTable();
	void ColorTable();
	void Body();

	void Out(char ch);
	void OutHex(byte b);
	void OutStr(const char *s);
	void OutVisualMark(VisualMark *);
	void Word(char *s);
	void Word(char *s, int val);
	void Char(byte ch);
	void OutPara(ParaStyle *ps);
	void OutTabs(const ParaTabs &pt);
	void OutCharStyle(CharStyle *cs);
	void LineBreak();

	int PixToTwips(int t);

	void PlainChar();
	void SetFont(GrFont f)
		{ font= f; }
	void SetSize(int s)
		{ size= s; }
	void SetColor(Ink *c)
		{ color= c; }
	void SetFace(GrFace fc, int b)
		{ face= (GrFace) (b ? face | fc : face & ~fc); }
	void SetFace(GrFace fc)
		{ face= fc; }
};

void RTF_Writer::Out(char ch)
{
	charpos++;
	if (charpos > 80)
		LineBreak();
	os.put(ch);
}

void RTF_Writer::OutHex(byte b)
{
	Out('\\');
	Out('\'');
	Out(hexChars[b/16]);
	Out(hexChars[b%16]);
}

void RTF_Writer::OutStr(const char *str)
{
	charpos+= strlen(str);
	if (charpos > 80)
		LineBreak();
	os << str;
}

inline int RTF_Writer::PixToTwips(int t)
{
	return t*20;
}

inline void RTF_Writer::Word(char *s)
{
	OutStr(form("\\%s ", s));
}

inline void RTF_Writer::Word(char *s, int val)
{
	OutStr(form("\\%s%d ", s, val));
}

void RTF_Writer::PlainChar()
{
	SetFont(GrFont(99)); // force output of font
	SetSize(12);
	SetColor(gInkBlack);
	SetFace(eFacePlain);

	Word("plain");
}

void RTF_Writer::LineBreak()
{
	charpos= 0;
	os.put('\n');
}

RTF_Writer::RTF_Writer(OStream &to) : os(to)
{
}

RTF_Writer::~RTF_Writer()
{
	SafeDelete(fm);
	SafeDelete(im);
}

void RTF_Writer::Doit(StyledText *t)
{
	st= t;
	charpos= 0;
	ignoreVObjects= FALSE;
	vobjectEscape= st->GetMarkChar();

	Begin();
	FontTable();
	ColorTable();
	Body();
	End();
}

void RTF_Writer::Begin()
{
	OutStr("{");
	Word("rtf", 1);
	Word("mac");
	Word("deff", 0);
}

void RTF_Writer::End()
{
	OutStr("}");
	os.flush();
}

void RTF_Writer::FontTable()
{
	fm= new RTF_FontMap();
	Iter next(gCharStyles->MakeIterator());
	CharStyle *cs;
	Font *fp;
	GrFont fnt;
	int i;

	OutStr("{");
	Word("fonttbl");
	while (cs= (CharStyle*) next()) {
		fp= cs->GetFont();
		fnt= fp->Fid();
		i= fm->Add(fnt);
		if (i != -1) {
			Word("f", i);
			Word(fm->Family(fnt));
			OutStr(fp->Name());
			Out(';');
		}
	}
	OutStr("}");
}

void RTF_Writer::ColorTable()
{
	im= new RTF_InkMap();
	Iter next(gCharStyles->MakeIterator());
	CharStyle *cs;
	Ink *ink;
	RGBColor *rgb;
	int i;

	OutStr("{");
	Word("colortbl");
	while (cs= (CharStyle*) next()) {
		ink= cs->GetInk();
		if (ink->IsKindOf(RGBColor)) {
			rgb= (RGBColor*)ink;
			i= im->Add(rgb);
			if (i != -1) {
				Word("red", rgb->GetRed());
				Word("green", rgb->GetGreen());
				Word("blue", rgb->GetBlue());
				Out(';');
			}
		}
		if (ink == gInkBlack || ink == gInkWhite) {
			i= im->Add(ink);
			if (i != -1) {
				int val= 0;
				if (ink == gInkWhite)
					val= 255;
				Word("red", val);
				Word("green", val);
				Word("blue", val);
				Out(';');
			}
		}
	}
	OutStr("}");
}

void RTF_Writer::Body()
{
	AutoTextIter next(st);
	int fChange= 0;
	int pChange= 0;
	int ch;

	Word("deftab", PixToTwips(st->GetDefTab()));

	while ((ch= next()) != cEOT) {
		int last= next->GetLastPos();
		if (last == pChange) {
			OutPara(st->GetParaStyle(last));
			PlainChar();
			OutCharStyle(st->GetCharStyle(last));
			pChange+= st->GetParaStyles()->RunSizeAt(pChange);
		}
		if (last == fChange) {
			fChange+= st->GetCharStyles()->RunSizeAt(fChange);
			OutCharStyle(st->GetCharStyle(last));
		}
		if (ch == vobjectEscape && st->IsVisualMark(last)) {
			VisualMark *gip= st->GetVisualMarkAt(last);
			// if (!ConvertTo(cDocTypeRtf, gip, os))
			if (gip)
					OutVisualMark(gip);
		}
		else
			Char((byte)ch);
	}
}

static struct parproptab {
	TxtParaProp prop;
	int deflt;
	char *word;
} ParPropTab[] = {
	{ eTxtPFirstIndent,      0,       "fi" },
	{ eTxtPLeft,             0,       "li" },
	{ eTxtPRight,            0,       "ri" },
	{ eTxtPSpacing,          0,       "sl" },
	{ eTxtPSpaceBefore,      0,       "sb" },
	{ eTxtPSpaceAfter,       0,       "sa" },
	{ eTxtPAlign,            0,       0 }
};

static struct aligntab {
	TxtParaAlign align;
	char *word;
} AlignTab[] = {
	{ eTxtParaLeft,      0 },
	{ eTxtParaRight,     "qr" },
	{ eTxtParaCenter,    "qc" },
	{ eTxtParaJustified, "qj" }
};

void RTF_Writer::OutPara(ParaStyle *ps)
{
	Word("pard");
	TxtParaAlign align= (TxtParaAlign)ps->GetProperty(eTxtPAlign);
	char *aword= AlignTab[align].word;
	if (aword)
		Word(aword);
	int prop;
	for (int i= 0; ParPropTab[i].word; i++) {
		prop= ps->GetProperty(ParPropTab[i].prop);
		if (prop != ParPropTab[i].deflt)
		Word(ParPropTab[i].word, PixToTwips(prop));
	}
	if (ps->GetProperty(eTxtPNoBreak))
		Word("keep");
	if (ps->GetProperty(eTxtPKeepNext))
		Word("keepn");
	const ParaTabs &tabs= ps->GetTabs();
	if (tabs.ntabs != 0)
		OutTabs(tabs);
}

static struct facetab {
	GrFace face;
	char *word;
	char *wordoff;
} FaceTab[] = {
	{ eFaceBold,      "b",       "b0" },
	{ eFaceItalic,    "i",       "i0" },
	{ eFaceUnderline, "ul",      "ul0" },
	{ eFaceOutline,   "outl",    "outl0" },
	{ eFaceShadow,    "shad",    "shad0" },
	{ eFacePlain,     "" ,        "" }
};

void RTF_Writer::OutCharStyle(CharStyle *cs)
{
	Font *fp= cs->GetFont();
	Ink *ink= cs->GetInk();
	if (ink != color) {
		Word("cf", im->Map(ink));
		SetColor(ink);
	}
	if (fp->Size() != size) {
		Word("fs", 2*fp->Size());
		SetSize(fp->Size());
	}
	if (fp->Fid() != font) {
		Word("f", fm->Map(fp->Fid()));
		SetFont(fp->Fid());
	}
	if (fp->Face() != face) {
		for (int i= 0; FaceTab[i].face != eFacePlain; i++) {
			int fbit= (int)FaceTab[i].face;
			int fold= (int)face&fbit;
			int fnew= (int)fp->Face()&fbit;
			if (fold != fnew) {
				if (fnew)
					Word(FaceTab[i].word);
				else
					Word(FaceTab[i].wordoff);
			}
			SetFace((GrFace)fbit, fnew != 0);
		}
	}
}
		
static struct tabtab {
	TxtTabStop kind;
	char *word;
} TabTab[] = {
	{ eTxtTLeft,      0 },
	{ eTxtTRight,    "tqr" },
	{ eTxtTCenter,   "tqc" },
	{ eTxtTDecimal,  "tqdec" }
};

void RTF_Writer::OutTabs(const ParaTabs &pt)
{
	for (int i= 0; i < pt.ntabs; i++) {
		TabStop *ts= &pt.stops[i];
		char *word= TabTab[(int)ts->kind].word;
		if (word)
			Word(word);
		Word("tx", PixToTwips(ts->x));
	}
}

void RTF_Writer::Char(byte c)
{
	switch(c) {
		case '\t':
			Word("tab");
			break;
		case '\r':
			Word("line");
			break;
		case '\0':
		case '\n':
			Word("par");
			break;
		case '\f':
			Word("page");
			break;
		
		case ':':
		case '{':
		case '}':
		case '\\':
			OutStr(form("%c%c",'\\', c));
			break;
		default:
			if (!Isascii(c))
				OutHex(c);
			else
				Out(c);
	}
}

void RTF_Writer::OutVisualMark(VisualMark *gip)
{
	// VObject comment, ignored by ET++ RTF reader
	OutStr("{");
	Word("vobjfollows");
	OutStr("[***ET++ Image***]");
	OutStr("}");

	// VObject destination
	if (!ignoreVObjects) {
		OutStr("\\*");
		OutStr("{");
		Word("etvobject");
		Word("vobjstart");
		OStream oios(os.getsbuf(), FALSE);
		oios << gip NL;
		oios.flush();
		OutStr("}");
	}
}

//---- RtfConverter ------------------------------------------------------------

class RtfConverter : public Converter {
public:
	MetaDef(RtfConverter);
	RtfConverter()
		{ }
	const char *AsString()
		{ return "Rich Text Format (RTF)"; }
	Object *Convert(Data *data, Class *want);
	bool CanConvert(Data *data, Class *want)
		{ return data->Type() == cDocTypeRtf && want->isKindOf(Meta(Text)); }
	bool CanConvertTo(Object *op)
		{ return op->IsKindOf(StyledText); }
	bool ConvertTo(Data *data, Object *op);
};

NewMetaImpl0(RtfConverter,Converter);

Object *RtfConverter::Convert(Data *data, Class *want)
{
	RTF_Global *d;

	IStream is(data->GetStreamBufForReading(), 0, TRUE);
	if (want->isKindOf(Meta(StyledText)))
		d= new RTF_Global(want, data);
	else if (want->isKindOf(Meta(Text)))
		d= new RTF_GlobalPureText(want, data);
	RTF_State(d).scan(is);
	return d->body;
}

bool RtfConverter::ConvertTo(Data *data, Object *op)
{
	if (op->IsKindOf(StyledText)) {
		OStream to((char*) data->Name());
		RTF_Writer rtfw(to);
		rtfw.Doit((StyledText*)op);
		return TRUE;
	}
	return FALSE;
}

